package cn.easyutil.project.service.mybatis;

import cn.easyutil.project.base.bean.Page;
import cn.easyutil.project.base.sqlExecuter.SQLExecuter;
import cn.easyutil.project.base.sqlExecuter.SqlBuilder;
import cn.easyutil.util.javaUtil.JsonUtil;
import cn.easyutil.util.javaUtil.LoggerUtil;
import cn.easyutil.util.javaUtil.ObjectUtil;
import cn.easyutil.util.javaUtil.StringUtil;
import org.apache.fop.fo.expr.PropertyException;
import org.apache.ibatis.builder.SqlSourceBuilder;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.BaseStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.scripting.xmltags.ForEachSqlNode;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.stereotype.Component;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Properties;


@Component
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class,Integer.class }) })
public class MybatisInterceptor implements Interceptor {

	private static String dialect = "mysql";
	private static String pageSqlId = "ISTPAGE";

	public Object intercept(Invocation ivk) throws Throwable {
		if (ivk.getTarget() instanceof RoutingStatementHandler) {
			RoutingStatementHandler statementHandler = (RoutingStatementHandler) ivk.getTarget();
			BaseStatementHandler delegate = (BaseStatementHandler) ObjectUtil.getAttributeValue(statementHandler, "delegate");
            ParameterHandler parameterHandler = delegate.getParameterHandler();
			MappedStatement mappedStatement = (MappedStatement) ObjectUtil.getAttributeValue(delegate, "mappedStatement");

			BoundSql boundSql = delegate.getBoundSql();
			// 获取请求参数
			Object parameterObject = boundSql.getParameterObject();

			// MyBatis中sql的id
			String id = mappedStatement.getId();

			String sql = boundSql.getSql();

			String _id = id.toUpperCase().substring(id.lastIndexOf(".") + 1);
			
            // 自动补填语句
			{
				boolean isAuto = true;
				if (_id.equals("ADD") || _id.equals("INSERT")) {
					sql = SqlBuilder.buildInsertSql(parameterObject);
				} else if (_id.equals("UPDATE")) {
					sql = SqlBuilder.buildUpdateSql(parameterObject);
				} else if (_id.equals("DELETE") || _id.equals("DEL")) {
					sql = SqlBuilder.buildDeleteSql(parameterObject);
				} else if (_id.equals("LISTPAGE")) {
					sql = SqlBuilder.buildSelectSql(parameterObject);
				} else if (_id.equals("SELECT") || _id.equals("LIST")) {
					sql = SqlBuilder.buildSelectSql(parameterObject);
				} else if (_id.equals("GET")) {
					sql = SqlBuilder.buildGetSql(parameterObject);
				}else if("COUNT".equals(_id)){
		    		sql = "select count(0) from (" + SqlBuilder.buildSelectSql(parameterObject) + ") as total";
		    	} else {
					isAuto = false;
				}

				if(isAuto) {
					SqlSource sqlSource = buildSqlSource(mappedStatement.getConfiguration(), sql, parameterObject);
		            List<ParameterMapping> parameterMappings = sqlSource.getBoundSql(parameterObject).getParameterMappings();
					sql = sqlSource.getBoundSql(parameterObject).getSql();
		            ObjectUtil.setAttribute(boundSql, "sql", sql);
					ObjectUtil.setAttribute(boundSql, "parameterMappings", parameterMappings);
				}
			}

			// 修改传过来的参数
            ObjectUtil.setAttribute(parameterHandler,"parameterObject",parameterObject);

			// 分页istPageLoad
			if (_id.endsWith(pageSqlId)) {
				if (parameterObject == null) {
					throw new NullPointerException("parameterObject尚未实例化！");
				}
				Page page  = null;
                Object bean = null;
                if(parameterObject instanceof SQLExecuter){
                	SQLExecuter executer = (SQLExecuter) parameterObject;
                	bean = executer.getBean();
                	page = ObjectUtil.getAttributeValue(bean, "page");
                	ObjectUtil.setAttribute(bean, "page", null);
                }else{
                	bean = parameterObject;
                	page = ObjectUtil.getAttributeValue(parameterObject, "page");
                	ObjectUtil.setAttribute(parameterObject, "page", null);
                }
				Connection connection = (Connection) ivk.getArgs()[0];
				String countSql = "select count(0) from (" + sql + ") as total";
				PreparedStatement countStmt = connection.prepareStatement(countSql);
				BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject);
				setParameters(countStmt, mappedStatement, countBS, parameterObject);
				ResultSet rs = countStmt.executeQuery();
				int count = 0;
				if (rs.next()) {
					count = rs.getInt(1);
				}
				rs.close();
				countStmt.close();
				if (page == null) {
					page = new Page();
					page.setCurrentPage(1);
					page.setTotalResult(count);
				}else{
					page.setTotalResult(count);
				}
				sql = generatePageSql(sql, page);
				ObjectUtil.setAttribute(bean, "page", page);
				ObjectUtil.setAttribute(boundSql, "sql", sql);
			}
			LoggerUtil.info(this.getClass(), "sql="+sql);
			LoggerUtil.info(this.getClass(), "sqlParam="+JsonUtil.beanToJson(parameterObject));
		}

		return ivk.proceed();
	}
	
	private SqlSource buildSqlSource(Configuration configuration, String originalSql, Object parementerObject) {
        SqlSourceBuilder builder = new SqlSourceBuilder(configuration);
        Map<String, Object> param = ObjectUtil.getNotNullAttributes(parementerObject);
        param.remove("page");
        return builder.parse(originalSql, parementerObject.getClass(), param);
    }

	@SuppressWarnings("unchecked")
	private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException {
		ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		if (parameterMappings != null) {
			Configuration configuration = mappedStatement.getConfiguration();
			TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
			MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
			for (int i = 0; i < parameterMappings.size(); i++) {
				ParameterMapping parameterMapping = parameterMappings.get(i);
				if (parameterMapping.getMode() != ParameterMode.OUT) {
					Object value;
					String propertyName = parameterMapping.getProperty();
					PropertyTokenizer prop = new PropertyTokenizer(propertyName);
					if (parameterObject == null) {
						value = null;
					} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
						value = parameterObject;
					} else if (boundSql.hasAdditionalParameter(propertyName)) {
						value = boundSql.getAdditionalParameter(propertyName);
					} else if ( propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX) && boundSql.hasAdditionalParameter(prop.getName())) {
						value = boundSql.getAdditionalParameter(prop.getName());
						if (value != null) {
							value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));
						}
					} else {
						value = metaObject == null ? null : metaObject.getValue(propertyName);
					}
					@SuppressWarnings("rawtypes")
					TypeHandler typeHandler = parameterMapping.getTypeHandler();
					if (typeHandler == null) {
						throw new ExecutorException(
								"There was no TypeHandler found for parameter "
										+ propertyName + " of statement "
										+ mappedStatement.getId());
					}
					typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType());
				}
			}
		}
	}

	private String generatePageSql(String sql, Page page) {
		if (page != null && !StringUtil.isEmpty(dialect)
				&& page.getShowCount() > 0) {
			StringBuffer pageSql = new StringBuffer();
			if ("oracle".equals(dialect)) {
				pageSql.append("select * from (select tmp_tb.*,ROWNUM row_id from (");
				pageSql.append(sql);
				pageSql.append(") as tmp_tb where ROWNUM<=");
				pageSql.append(page.getCurrentResult() + page.getShowCount());
				pageSql.append(") where row_id>");
				pageSql.append(page.getCurrentResult());
			} else if ("mysql".equalsIgnoreCase(dialect)) {
				pageSql.append("select * from (");
				pageSql.append(sql);
				pageSql.append(" limit ")
						.append((page.getCurrentPage()-1) * page.getShowCount()).append(",")
						.append(page.getShowCount());
				pageSql.append(") as tmp_tb ");
			}
			return pageSql.toString();
		} else {
			return sql;
		}
	}

	public Object plugin(Object arg0) {
		return Plugin.wrap(arg0, this);
	}

	public void setProperties(Properties p) {
		dialect = p.getProperty("dialect");
		if (StringUtil.isEmpty(dialect)) {
			try {
				throw new PropertyException("dialect property is not found!");
			} catch (PropertyException e) {
				e.printStackTrace();
			}
		}
		pageSqlId = p.getProperty("pageSqlId");
		if (StringUtil.isEmpty(pageSqlId)) {
			try {
				throw new PropertyException("pageSqlId property is not found!");
			} catch (PropertyException e) {
				e.printStackTrace();
			}
		}
	}
}
